/*
 * JavaScript MD5
 * https://github.com/blueimp/JavaScript-MD5
 *
 * Copyright 2011, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 *
 * Based on
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*global unescape, define, module */

;(function ($) {
  'use strict'

  /*
  * Add integers, wrapping at 2^32. This uses 16-bit operations internally
  * to work around bugs in some JS interpreters.
  */
  function safe_add (x, y) {
    var lsw = (x & 0xFFFF) + (y & 0xFFFF)
    var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
    return (msw << 16) | (lsw & 0xFFFF)
  }

  /*
  * Bitwise rotate a 32-bit number to the left.
  */
  function bit_rol (num, cnt) {
    return (num << cnt) | (num >>> (32 - cnt))
  }

  /*
  * These functions implement the four basic operations the algorithm uses.
  */
  function md5_cmn (q, a, b, x, s, t) {
    return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
  }
  function md5_ff (a, b, c, d, x, s, t) {
    return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)
  }
  function md5_gg (a, b, c, d, x, s, t) {
    return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)
  }
  function md5_hh (a, b, c, d, x, s, t) {
    return md5_cmn(b ^ c ^ d, a, b, x, s, t)
  }
  function md5_ii (a, b, c, d, x, s, t) {
    return md5_cmn(c ^ (b | (~d)), a, b, x, s, t)
  }

  /*
  * Calculate the MD5 of an array of little-endian words, and a bit length.
  */
  function binl_md5 (x, len) {
    /* append padding */
    x[len >> 5] |= 0x80 << (len % 32)
    x[(((len + 64) >>> 9) << 4) + 14] = len

    var i
    var olda
    var oldb
    var oldc
    var oldd
    var a = 1732584193
    var b = -271733879
    var c = -1732584194
    var d = 271733878

    for (i = 0; i < x.length; i += 16) {
      olda = a
      oldb = b
      oldc = c
      oldd = d

      a = md5_ff(a, b, c, d, x[i], 7, -680876936)
      d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586)
      c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819)
      b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330)
      a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897)
      d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426)
      c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341)
      b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983)
      a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416)
      d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417)
      c = md5_ff(c, d, a, b, x[i + 10], 17, -42063)
      b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162)
      a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682)
      d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101)
      c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290)
      b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329)

      a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510)
      d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632)
      c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713)
      b = md5_gg(b, c, d, a, x[i], 20, -373897302)
      a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691)
      d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083)
      c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335)
      b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848)
      a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438)
      d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690)
      c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961)
      b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501)
      a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467)
      d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784)
      c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473)
      b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734)

      a = md5_hh(a, b, c, d, x[i + 5], 4, -378558)
      d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463)
      c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562)
      b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556)
      a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060)
      d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353)
      c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632)
      b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640)
      a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174)
      d = md5_hh(d, a, b, c, x[i], 11, -358537222)
      c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979)
      b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189)
      a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487)
      d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835)
      c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520)
      b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651)

      a = md5_ii(a, b, c, d, x[i], 6, -198630844)
      d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415)
      c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905)
      b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055)
      a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571)
      d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606)
      c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523)
      b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799)
      a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359)
      d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744)
      c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380)
      b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649)
      a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070)
      d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379)
      c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259)
      b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551)

      a = safe_add(a, olda)
      b = safe_add(b, oldb)
      c = safe_add(c, oldc)
      d = safe_add(d, oldd)
    }
    return [a, b, c, d]
  }

  /*
  * Convert an array of little-endian words to a string
  */
  function binl2rstr (input) {
    var i
    var output = ''
    var length32 = input.length * 32
    for (i = 0; i < length32; i += 8) {
      output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF)
    }
    return output
  }

  /*
  * Convert a raw string to an array of little-endian words
  * Characters >255 have their high-byte silently ignored.
  */
  function rstr2binl (input) {
    var i
    var output = []
    output[(input.length >> 2) - 1] = undefined
    for (i = 0; i < output.length; i += 1) {
      output[i] = 0
    }
    var length8 = input.length * 8
    for (i = 0; i < length8; i += 8) {
      output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32)
    }
    return output
  }

  /*
  * Calculate the MD5 of a raw string
  */
  function rstr_md5 (s) {
    return binl2rstr(binl_md5(rstr2binl(s), s.length * 8))
  }

  /*
  * Calculate the HMAC-MD5, of a key and some data (raw strings)
  */
  function rstr_hmac_md5 (key, data) {
    var i
    var bkey = rstr2binl(key)
    var ipad = []
    var opad = []
    var hash
    ipad[15] = opad[15] = undefined
    if (bkey.length > 16) {
      bkey = binl_md5(bkey, key.length * 8)
    }
    for (i = 0; i < 16; i += 1) {
      ipad[i] = bkey[i] ^ 0x36363636
      opad[i] = bkey[i] ^ 0x5C5C5C5C
    }
    hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
    return binl2rstr(binl_md5(opad.concat(hash), 512 + 128))
  }

  /*
  * Convert a raw string to a hex string
  */
  function rstr2hex (input) {
    var hex_tab = '0123456789abcdef'
    var output = ''
    var x
    var i
    for (i = 0; i < input.length; i += 1) {
      x = input.charCodeAt(i)
      output += hex_tab.charAt((x >>> 4) & 0x0F) +
      hex_tab.charAt(x & 0x0F)
    }
    return output
  }

  /*
  * Encode a string as utf-8
  */
  function str2rstr_utf8 (input) {
    return unescape(encodeURIComponent(input))
  }

  /*
  * Take string arguments and return either raw or hex encoded strings
  */
  function raw_md5 (s) {
    return rstr_md5(str2rstr_utf8(s))
  }
  function hex_md5 (s) {
    return rstr2hex(raw_md5(s))
  }
  function raw_hmac_md5 (k, d) {
    return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))
  }
  function hex_hmac_md5 (k, d) {
    return rstr2hex(raw_hmac_md5(k, d))
  }

  function md5 (string, key, raw) {
    if (!key) {
      if (!raw) {
        return hex_md5(string)
      }
      return raw_md5(string)
    }
    if (!raw) {
      return hex_hmac_md5(key, string)
    }
    return raw_hmac_md5(key, string)
  }

  if (typeof define === 'function' && define.amd) {
    define(function () {
      return md5
    })
  } else if (typeof module === 'object' && module.exports) {
    module.exports = md5
  } else {
    $.md5 = md5
  }
}(this));

/**
 * 解锁的类
 */
(function(){
    
		/**
		 * 外部API
		 * 初始化DevinH5lock
		 * options : {
		 * 		chooseType: n  绘制 n*n 的 圆圈数量
		 * 		commonCircleColor: 圆形解锁框的普通颜色
		 * 		sureCircleColor: 圆形解锁框的正确解锁颜色
		 * 		errorCircleColr: 圆形解锁框的错误解锁颜色
		 * }
		 * callback : function(data) {
		 * 		data对象 {
		 * 			"generatePassword":"", //本次滑动之后生成的md5字符串
        	 *   		"operation":"CREATE",  //本次操作类型，如果传入validatePassword不为undefined 则为VALIDATE，validatePassword为undefined为CREATE    	    		"validateStatus":false
		 * 			"validateStatus":false //验证是否通过，如果operation是CREATE 则为false ；operation是VALIDATE 验证通过是true，否则是false
		 * 		}
		 * }
		 */
        window.DevinH5lock = function(options,callback){
            this.chooseType = options.chooseType || 3;
            this.commonCircleColor = options.commonCircleColor || '#CFE6FF';
            this.sureCircleColor = options.sureCircleColor || 'green';
            this.errorCircleColr = options.errorCircleColr || 'red';
            this.cirleSize = options.circleSize || 10 ;
            this.lineWidth = options.lineWidth || 3 ;
            this.md5Perfix = options.md5Perfix || "$^$";
            this.validatePassword = options.validatePassword || undefined ;
            this.disableDevinH5lock = options.disableDevinH5lock || false ;
            
            if(Object.prototype.toString.call(callback)==='object Function'){
            		this.callback = callback ;
            }else {
            		this.callback = function(data){
            			console.log(data);
            		}
            }
        };
        
        /**
         * 比较2次密码
         * @param psw1
         * @param psw2
         */
        DevinH5lock.prototype.checkPassword = function(psw1, psw2) {
            return psw1 === psw2;
        }
        
        /**
         * 外部API，在需要验证的时候调用，如果不设置参数，则设置为undefined
         * 设置验证密码
         * @param validatePassword
         */
        DevinH5lock.prototype.setValidatePassword = function(validatePassword) {
        		if(arguments.length == 0){
        			this.validatePassword = undefined ;
        		}else {
        			this.validatePassword = validatePassword ;
        		}
        		this.reset();
        }
        
        /**
         * 设置是否禁用，不设置参数，代表当前可用。
         * @param disableDevinH5lock
         */
        DevinH5lock.prototype.setDisableDevinH5lock = function(disableDevinH5lock) {
        		if(arguments.length == 0) {
        			this.disableDevinH5lock = false ;
        		}else {
        			this.disableDevinH5lock = disableDevinH5lock ;
        		}
        		this.reset();
        }
        
        /**
         * 初始化密码的圆形
         * @param x 圆形的x坐标起点
         * @param y 圆形的y坐标起点
         */
        DevinH5lock.prototype.drawCle = function(x, y) {
            this.ctx.strokeStyle = this.commonCircleColor;
            this.ctx.lineWidth = 2;
            this.ctx.beginPath();
            this.ctx.arc(x, y, this.r + this.cirleSize, 0, Math.PI * 2, true);
            this.ctx.closePath();
            this.ctx.stroke();
        }
        
        /**
         * 绘制圆形边缘状态，当成功或失败的时候可以使用
         */
         DevinH5lock.prototype.drawStatusPoint = function(color) { // 初始化状态线条
            for (var i = 0 ; i < this.lastPoint.length ; i++) {
                this.ctx.strokeStyle = color;
                this.ctx.beginPath();
                this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r+this.cirleSize, 0, Math.PI * 2, true);
                this.ctx.closePath();
                this.ctx.stroke();
            }
        }
        
        /**
         * 绘制圆形点的圆心
         */
        DevinH5lock.prototype.drawPoint = function() { // 初始化圆心
            for (var i = 0 ; i < this.lastPoint.length ; i++) {
                this.ctx.fillStyle = this.commonCircleColor;
                this.ctx.beginPath();
                this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r / 2, 0, Math.PI * 2, true);
                this.ctx.closePath();
                this.ctx.fill();
            }
        }
        
        /**
         * 绘制解锁的轨迹
         * @param po 当前手所在的坐标
         * @param lastPoint 经过的点
         */
        DevinH5lock.prototype.drawLine = function(po, lastPoint) {// 解锁轨迹
            this.ctx.beginPath();
            this.ctx.lineWidth = this.lineWidth;
            this.ctx.moveTo(this.lastPoint[0].x, this.lastPoint[0].y);
            for (var i = 1 ; i < this.lastPoint.length ; i++) {
                this.ctx.lineTo(this.lastPoint[i].x, this.lastPoint[i].y);
            }
            this.ctx.lineTo(po.x, po.y);
            this.ctx.stroke();
            this.ctx.closePath();
 
        }
        
        /**
         * 创建解锁点的坐标，根据canvas的大小来平均分配半径
         */
        DevinH5lock.prototype.createCircle = function() {
 
            var n = this.chooseType;
            var count = 0;
            this.r = this.ctx.canvas.width / (2 + 4 * n);// 公式计算
            this.lastPoint = [];
            this.arr = [];
            this.restPoint = [];
            var r = this.r;
            for (var i = 0 ; i < n ; i++) {
                for (var j = 0 ; j < n ; j++) {
                    count++;
                    var obj = {
                        x: j * 4 * r + 3 * r,
                        y: i * 4 * r + 3 * r,
                        index: count
                    };
                    this.arr.push(obj);
                    this.restPoint.push(obj);
                }
            }
            this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
            for (var i = 0 ; i < this.arr.length ; i++) {
                this.drawCle(this.arr[i].x, this.arr[i].y);
            }
        }
        
        /**
         * 获取当前touch的 position点
         * @param e
         */
        DevinH5lock.prototype.getPosition = function(e) {
            var rect = e.currentTarget.getBoundingClientRect();
            var po = {
                x: e.touches[0].clientX - rect.left,
                y: e.touches[0].clientY - rect.top
              };
            return po;
        }
        
        /**
         * 核心变换方法在touchmove时候调用
         * @param po
         */
        DevinH5lock.prototype.update = function(po) {// 核心变换方法在touchmove时候调用
            this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
 
            for (var i = 0 ; i < this.arr.length ; i++) { // 每帧先把面板画出来
                this.drawCle(this.arr[i].x, this.arr[i].y);
            }
 
            this.drawPoint(this.lastPoint);// 每帧轨画迹
            this.drawLine(po, this.lastPoint);// 每帧画圆心
 
            for (var i = 0 ; i < this.restPoint.length ; i++) {
                if (Math.abs(po.x - this.restPoint[i].x) < this.r && Math.abs(po.y - this.restPoint[i].y) < this.r) {
                    this.drawPoint(this.restPoint[i].x, this.restPoint[i].y);
                    this.lastPoint.push(this.restPoint[i]);
                    this.restPoint.splice(i, 1);
                    break;
                }
            }
        }
        
        /**
         * 生成MD5的密码字符串
         */
        DevinH5lock.prototype.generateMD5Password = function(locationArray) {
        		var indexMd5Array = [] ;
        		var indexMd5String = "" ;
        		if(Object.prototype.toString.call(locationArray) == '[object Array]'){
        			for(var index in locationArray){
        				if(locationArray[index].index){
        					var locationMd5 = md5(this.md5Perfix + locationArray[index].index);
        					indexMd5Array.push(locationMd5);
        					indexMd5String += (this.md5Perfix + locationMd5) ;
        				}
        			}
        			return md5(indexMd5String);
        		}
        		throw new Error("location array must be array");
        }
        
        //完成触摸之后
        DevinH5lock.prototype.finishTouch = function(locationArray){
        	    var data = {
        	    		"generatePassword":"",
        	    		"operation":"CREATE", //CREATE  VALIDATE
        	    		"validateStatus":false
        	    };
        		if(typeof(this.validatePassword) === 'undefined') {
        			//获取当前界面
				data.generatePassword = this.generateMD5Password(locationArray);
				data.operation = "CREATE";
        		} else {
        			data.generatePassword = this.generateMD5Password(locationArray);
				data.operation = "VALIDATE";
				data.validateStatus = this.checkPassword(this.validatePassword,data.generatePassword) ;
        			if(data.validateStatus){
        				this.drawStatusPoint(this.sureCircleColor);
        			} else {
        				this.drawStatusPoint(this.errorCircleColr);
        			}
        		}
        		//回调数据
        		this.callback(data);
        }
                
        DevinH5lock.prototype.setChooseType = function(type){
            chooseType = type;
            init();
        }
       
        DevinH5lock.prototype.initDom = function(){
            var wrap = document.createElement('div');
            var str = '<canvas id="canvas" width="330" height="330" style="background-color: #305066;display: inline-block;margin-top: 60px;"></canvas>';
            wrap.setAttribute('style','position: absolute;top:0;left:0;right:0;bottom:0;');
            wrap.innerHTML = str;
            document.body.appendChild(wrap);
        }
        
        DevinH5lock.prototype.init = function() {
            this.initDom();
            this.lastPoint = [];
            this.touchFlag = false;
            this.canvas = document.getElementById('canvas');
            this.ctx = this.canvas.getContext('2d');
            this.createCircle();
            this.bindEvent();
        }
        
        //重置
        DevinH5lock.prototype.reset = function() {
            this.createCircle();
        }
        
        DevinH5lock.prototype.bindEvent = function() {
            var self = this;
            this.canvas.addEventListener("touchstart", function (e) {
                if(self.disableDevinH5lock){
                		return ;
                }
                e.preventDefault();// 某些android 的 touchmove不宜触发 所以增加此行代码
                 var po = self.getPosition(e);
                 for (var i = 0 ; i < self.arr.length ; i++) {
                    if (Math.abs(po.x - self.arr[i].x) < self.r && Math.abs(po.y - self.arr[i].y) < self.r) {
                        self.touchFlag = true;
                        self.drawPoint(self.arr[i].x,self.arr[i].y);
                        self.lastPoint.push(self.arr[i]);
                        self.restPoint.splice(i,1);
                        break;
                    }
                 }
             }, false);
             
             this.canvas.addEventListener("touchmove", function (e) {
             	if(self.disableDevinH5lock){
                		return ;
               }
                if (self.touchFlag) {
                    self.update(self.getPosition(e));
                }
             }, false);
             
             this.canvas.addEventListener("touchend", function (e) {
             	if(self.disableDevinH5lock){
                		return ;
               }
                 if (self.touchFlag) {
                     self.touchFlag = false;
					self.finishTouch(self.lastPoint);
                     setTimeout(function(){
                        self.reset();
                    }, 300);
                 }
             }, false);
             
             document.addEventListener('touchmove', function(e){
                e.preventDefault();
             },false);
           	
        }
})();